package com.client.koshercook.server;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import com.google.appengine.api.datastore.*;
import com.google.appengine.api.users.User;

public class DataStore {
	
	public static final int PAGE_SIZE = 40;

	public static final int HISTORY_SIZE = 10;
	
	/** This class should not be instanced */
	private DataStore(){}

	
	/** deletes all of the recipes */
	public static void deleteAllRecipes(){
		System.out.println("deleteAllRecipes: start");
		PersistenceManager pm = PMF.get().getPersistenceManager();
  
        Query query = pm.newQuery(Recipes.class);
        
        query.deletePersistentAll();
        
        query.closeAll();       
        System.out.println("deleteAllRecipes: end");
	}

	
	/** add a single recipe to the data store (not including the required recipeInfos)*/
	public static void addRecipe(Recipes recipe){
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	System.out.println("DS: try to make presistent...");
            pm.makePersistent(recipe);
            System.out.println("DS: recipe add "+ recipe.getRecipeName());
        } catch(Exception e){
        	System.out.println("!can't make persistent! details: "+e.getMessage());
        }
        finally {
            pm.close();
        }
	}
	
	/** delete a recipe from DS (including his RecipeInfos)*/
	public static void deleteRecipe(String rec_link){
		Key rec_key = KeyFactory.createKey(Recipes.class.getSimpleName(), rec_link);
		Recipes recipe = DataStore.getRecipeFromKey(rec_key,false);
		DataStore.updateRecipeInfoForDelete(recipe); //delete recipeInfos
		deleteObject(Recipes.class,rec_key); //delete recipe
		//delete alters objects. I don't know if it's good or not
		for(ingWithAlters iwa : recipe.getAlters()) {
			deleteObject(ingWithAlters.class,iwa.getKey());
			System.out.println("DS: deleted iwa from recipe"+rec_key.getName());
		}
	}
	
	/** delete RecipeInfo object from the list in every ingredient needed
	 * @when it's used after the Recipe is deleted*/
	private static void updateRecipeInfoForDelete(Recipes recipe) {
		Set<Key> ing_keys = recipe.getSetOfIngredientsAndAlters();			
		for(Key k : ing_keys) {
			deleteRecipeInfoInIngredient(k,recipe.getLink());
		}
	}
	
	/** delete RecipeInfo object from the list in the ingredient specified
	 * @when it's used after the Recipe is deleted*/
	private static void deleteRecipeInfoInIngredient(Key ing_key,String rec_link) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		RecipeInfo ri=null;
		Ingredient ingredient=null;
		try {
			ingredient = pm.getObjectById(Ingredient.class, ing_key);
			System.out.println("DS: RI size before = "+ingredient.getInRecipes().size() + ", num="+ingredient.getNumOfRecipes());
			ri = ingredient.deleteInRecipes(rec_link);//delete RecipeInfo to the list of every ingredient	
			ingredient.createPositions();
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfo! details: "+e.getMessage());
		} finally {		
			System.out.println("DS: RI size after = "+ingredient.getInRecipes().size()+ ", num="+ingredient.getNumOfRecipes());
	    }
		pm.close();//close the pm saving the changes
		if(ri!=null) deleteObject(RecipeInfo.class,ri.getObjectKey());
	}
	
	/** delete RecipeInfo object from the list in every ingredient needed
	 * @param rec_link the recipe (link) to delete
	 * @param ings the ingredients in the recipe
	 * @example DataStore.updateRecipeInfoForDeleteManually("matkonkasher5","סוכר","שמן סויה","שמן זית");
	 * */	
	public static void updateRecipeInfoForDeleteManually(String rec_link, String ... ings) {		
		for(String str : ings) {
			Key k = KeyFactory.createKey(Ingredient.class.getSimpleName(), str);
			deleteRecipeInfoInIngredient(k,rec_link);
		}
	}
	
	/** delete an object from DS. */
	@SuppressWarnings("unchecked")
	public static void deleteObject(Class cls , Key key) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
        	System.out.println("DS: try to delete object "+key);
            pm.deletePersistent(pm.getObjectById(cls, key)); //delete RecipeInfo
            System.out.println("DS: object deleted");
        } catch(Exception e){
        	System.out.println("!can't delete object! details: "+e.getMessage());
        }
        finally {
            pm.close();
        }
		
	}


	/** updates the recipe and recipeInfos grade,
	 * and returns the new recipe grade */
	public static int updateRecipeGrade(String link, int userGrade){
		int g=-1;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes rec=null;
		List<Key> ings = new ArrayList<Key>();
		Key key = KeyFactory.createKey(Recipes.class.getSimpleName(), link);
		try {
			rec = pm.getObjectById(Recipes.class, key);
			rec.computeNewGrade(userGrade);
			ings.addAll(rec.getIngredients());
			g=rec.getGrade();
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfo! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
		
		for(Key k : ings) updateRecipeInfoGrade(k,g,link);
		
		return g;
	}
	
	/** updates ingredients' recipeInfo's grade
	 * @param ing_k - the key of the ingredient containing RI
	 * @param g - the grade to be set
	 * @param link - the link of the recipe whose grade was changed
	 */
	private static void updateRecipeInfoGrade(Key ing_k, int g,String link) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient ing=null;
		try {
			ing = pm.getObjectById(Ingredient.class, ing_k);
			for(RecipeInfo ri :ing.getInRecipes()) {
				if((ri.getLink()).equals(link)) {
					ri.setGrade(g);
					break;
				}		
			}
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfoGrade! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	/** search the data store for recipes by a list of ingredients AND SORTS THE RESULTS
	 *  @param page - what page of recipes to show. page*NumberOfWindows*RECIPES_IN_WINDOW = offset
	 *  @param andFlag - use to search recipes with all the ingredients
	 *  @param kosher - use null for all, use other for setting a restriction
	 *  @return a list of recipes with that ingredient, or an empty list */
	public static List<KeyWithWeight> recipeByIngredientsSorted(List<Key> ings, List<Key> mustIngs, int page,Accounts acc, short kosher,String type,List<String> forbiddenSites,int cookTimeLimit){
		List<KeyWithWeight> all;
		List<KeyWithWeight> resKeysWeight;
		
		List<String> noSites = new ArrayList<String>();
		for(String site : forbiddenSites) {
			if(site.equals("כיפה")) noSites.add("http://www.kipa.co.il/food/matkon/");
	    	if(site.equals("השף הלבן")) noSites.add("http://www.chef-lavan.co.il/tabid/53/RecipeID/");
	    	if(site.equals("רוטב")) noSites.add("http://www.rotev.co.il/last_video.aspx?recipe_id=");
	    	if(site.equals("שטיבל")) noSites.add("http://www.stybel.co.il/recipe_full.php?id=");
	    	if(site.equals("כושר-קוק")) noSites.add("matkonkasher");
		}
		if(cookTimeLimit==-1) cookTimeLimit=Integer.MAX_VALUE;
		

		page = page/4;
		
		if(mustIngs.size()>0){
			all = DataStore.recipeByIngredientsAND(mustIngs, ings,acc, kosher,type,page,noSites,cookTimeLimit);
		}
		else if(ings.size()==1) {
			//here musts = the only ing, and other ings = empty list (no musts)
			all = DataStore.recipeByIngredientsAND(ings, mustIngs,acc, kosher,type,page,noSites,cookTimeLimit); 
		}
		else{
			all = DataStore.recipeByIngredientsORefficient(ings,acc, kosher,type,page,noSites,cookTimeLimit);
		}
		if(all==null || all.isEmpty() ){	//no recipes found
			return new ArrayList<KeyWithWeight>();
		}

		if(all.size() <= PAGE_SIZE*(page+1)){ //this is the last page - TODO: how to notify client this is the last page?
			resKeysWeight = all.subList(PAGE_SIZE*page, all.size());
		}else{
			resKeysWeight = all.subList(PAGE_SIZE*page, PAGE_SIZE*(page+1));
		}
	
		
//		resKeysWeight = all.subList(0,all.size());

		return resKeysWeight;
	}
	
	/** search the data store for recipes by a list of ingredients  
     *  @return a list of recipes with that ingredient, null if list is empty */ 
    public static List<KeyWithWeight> recipeByIngredientsOR(List<Key> ings,Accounts acc, short kosher,String type, int reqNum){ 
    	

    	List<RecipeInfo> infos = new ArrayList<RecipeInfo>(); 
    	List<KeyWithWeight> finalL = new ArrayList<KeyWithWeight>();	
    	
    	SortedSet<Ingredient> ingredientByNum = new TreeSet<Ingredient>();
    	SortedSet<KeyWithWeight> mergeList = new TreeSet<KeyWithWeight>();
    	int ingsSize = ings.size();
    	
    	
    	// Search limits
    	int iter = (reqNum+1)*PAGE_SIZE;
    	int iter2;
    	if (iter==PAGE_SIZE)
    		iter2 = iter;
    	else
    		iter2 = iter - PAGE_SIZE;
    	
    	/*find the list of numOfRecipes per Ingredient and the total sum*/
    	int sum=0;
    	int tempNum = 0;
    	int high;
    	int low;
       	//int numRecipeTable[][] = new int[2][ingsSize];
       	
    	List<Ingredient> ingredients = DataStore.getIngredientsFromKeys(ings,2*iter);
    	high = ingredients.get(0).getNumOfRecipes();
    	low = ingredients.get(0).getNumOfRecipes();
    		
    	
    	for(Ingredient ing : ingredients){
    		//only once per ing, after the position feature was added
//            if(ing.getInRecipesPositions()==null || ing.getInRecipesPositions().size()!=ing.getInRecipes().size()) { 
//                ing.updatePositions();
//                updateIngredientInRecipesPositions(ing.getKey());
//            }
    		tempNum = ing.getNumOfRecipes();
    		sum += tempNum;
    		ingredientByNum.add(ing);
    		if (high < tempNum)
    			high = tempNum;
    		if (low > tempNum)
    			low = tempNum;
    	}
    	
		if (sum < iter && iter==PAGE_SIZE){						//not enough elements to filter, sort all
	    	for(Ingredient ing : ingredients){
	    		infos = ing.getInRecipes();
		    	for(RecipeInfo r : infos) {
		    		if(r.suitFilters(kosher,type)){
		    			mergeList.add(new KeyWithWeight(r,ings,acc));
		    		}
		    	}
	    	}
		}
		
		else{
				
	    	/* start choosing from the list with the higher number of recipes 
	    	 * save worst grade in the list. If possible to find better then try more*/
			float avg =  sum/ingsSize;                               //calculate the relative number of elements to take from each ingredient 
			int ratio;
			if (high==low)
				ratio = 0;
			else
				ratio = Math.round(sum/(high-low));
			int fromEach = Math.round(iter2/ingsSize);
			int avgL = 0;
			int index;
			int length = 0;
			int numRecipesUsed[] = new int[ingsSize];
			int toTake = 0;
			SortedSet<Key> recipesKeys = new TreeSet<Key>(); 
			for(index=0;index<ingsSize;index++)
				numRecipesUsed[index]=0;
			
			index=0;
					
			List<RecipeInfo> allNumOfRecipes;
			
	    	for(Ingredient ing : ingredientByNum){   					    //start from the elements with the smallest number of recipes
	    		tempNum = ing.getNumOfRecipes();							//this enable to choose more recipes from the elements with the higher number of recipes
				sum = 0;  													//actually used from ingredient

				
				while(true){
		    		if (tempNum < avg){
		    			if (numRecipesUsed[index]>0)	avgL++;            //we can take according to the ingredient position
		    			toTake = fromEach - ratio - sum;
		    			allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index], toTake);
		    			if(allNumOfRecipes.size() == 0){
		    				numRecipesUsed[index] = -1;  //mark this index
		    				break;
		    			}
	    				numRecipesUsed[index] += allNumOfRecipes.size();
		    		}
		    		else{		
		    			if (fromEach+ratio >= tempNum){  		    				
		    				allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index], Math.min(iter2-length,tempNum));
			    			if(allNumOfRecipes.size() == 0){
			    				numRecipesUsed[index] = -1;  //mark this index
			    				break;
			    			}
		    				numRecipesUsed[index] += allNumOfRecipes.size();
		    			}
		    			else{							//we can take according to the ingredient position and the loop history
			    			if(index<ingsSize-1){ 	    				
			    				toTake = Math.min(fromEach + ratio - sum + Math.round((iter2-length)/(ingsSize-index+1)),iter2-length);
			    				allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index], toTake);
				    			if(allNumOfRecipes.size() == 0){
				    				numRecipesUsed[index] = -1;  //mark this index
				    				break;
				    			}
			    				numRecipesUsed[index] += allNumOfRecipes.size();
			    			}
			    			else{
			    				allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index], iter2-length);             //complete to the end
				    			if(allNumOfRecipes.size() == 0){
				    				numRecipesUsed[index] = -1;  //mark this index
				    				break;
				    			}
			    				numRecipesUsed[index] += allNumOfRecipes.size();
			    			}
		    			}
		    		}	    		
		    		
		    		if (iter != iter2){
		    			//create the recipes name list
		    			for (RecipeInfo r : allNumOfRecipes) {
		    				if(r.suitFilters(kosher,type))
		    					recipesKeys.add(r.getKey());
			    		}
		    			high = recipesKeys.size();
			    		sum += high - length;
			    		length = high;
		    		}else{
		                //create the recipes list
		    			for(RecipeInfo r : allNumOfRecipes) {
				    		if(r.suitFilters(kosher,type)){
				    			mergeList.add(new KeyWithWeight(r,ings,acc)); 
				    		}
		    			}		
		    			high = mergeList.size();
			    		sum += high - length;
			    		length = high;
		    		}
		    		
		    		if(length == iter2){
		    			break;
		    		}
	    		}
	    		index++;
	    	}

	    	while(length < iter2){                   //complete in case we jump due to ratio constraint
	    		index = 0;
	    		for(Ingredient ing : ingredientByNum){  
	    			if (numRecipesUsed[index]== -1){
	    				index++;
	    				continue;
	    			}
	    			allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index],iter2 - length);
	    			if(allNumOfRecipes.size() == 0){
	    				numRecipesUsed[index] = -1;  //mark this index
	    				index++;
	    				continue;
	    			}
    				numRecipesUsed[index] += allNumOfRecipes.size();
    				if (iter==iter2){
		    			for(RecipeInfo r : allNumOfRecipes) {
				    		if(r.suitFilters(kosher,type)){
				    			mergeList.add(new KeyWithWeight(r,ings,acc)); 
				    		}
				    	}
		    			length = mergeList.size();
    				}else{
		    			for (RecipeInfo r : allNumOfRecipes) {
		    				if(r.suitFilters(kosher,type))
		    					recipesKeys.add(r.getKey());
			    		}
		    			length = recipesKeys.size();
    				}
    				if (length==iter2)
    					break;
	    			index++;
	    		}
	    		if (numRecipesUsed[ingsSize-1] == -1)        //no more Recipes
	    			break;
	    	}

	    	length = 0;
	    	if(iter2<iter){					// can use the info we collected and build the list as if we were actually calculate it
	    		while(length < PAGE_SIZE){
	    			toTake = Math.max(1,Math.round((PAGE_SIZE-length)/ingsSize));
	    			index=0;
		    		for(Ingredient ing : ingredientByNum){
		    			if (numRecipesUsed[index]== -1){
		    				index++;
		    				continue;
		    			}
		    			allNumOfRecipes = ing.getFirstInRecipes(numRecipesUsed[index],toTake);
		    			if(allNumOfRecipes.size() == 0){
		    				numRecipesUsed[index] = -1;  //mark this index
		       				index++;
		    				continue;
		    			}
		    			numRecipesUsed[index] += allNumOfRecipes.size();
		    			for(RecipeInfo r : allNumOfRecipes) {
				    		if(r.suitFilters(kosher,type)){
				    			mergeList.add(new KeyWithWeight(r,ings,acc)); 
				    		}
				    	}
		    			length = mergeList.size();		    			
	    				if (length==PAGE_SIZE)
	    					break;
	    				index++;
		    		}
		    		if (numRecipesUsed[ingsSize-1] == -1)        //no more Recipes
		    			break;
	    		}
	    	}		
		}
		    	
		
    	finalL.addAll(mergeList);
		return finalL; 
    }

    
    /** Remove the duplicated recipes from the list*/
    private static List<KeyWithWeight> removeDuplicate_KeyWithWeight(List<KeyWithWeight> arlList) {
    	Set<Key> set = new HashSet<Key>();
    	List<KeyWithWeight> newList = new ArrayList<KeyWithWeight>();
    	for (Iterator<KeyWithWeight> iter = arlList.iterator();    iter.hasNext(); ) {
    		KeyWithWeight element = iter.next();
	    	if (set.add(element.getKey()))
	    	newList.add(element);
	    	}
    	return newList;
    }
    
    /** Remove the duplicated Ingredients from the list*/
    private static List<Key> removeDuplicate_Key(List<Key> arlList) {
    	Set<String> set = new HashSet<String>();
    	List<Key> newList = new ArrayList<Key>();
    	for (Iterator<Key> iter = arlList.iterator();    iter.hasNext(); ) {
    		Key element = iter.next();
	    	if (set.add(element.getName()))
	    	newList.add(element);
	    	}
    	return newList;
    }
    
    /** search the data store for recipes by a list of ingredients  
     *  @return a list of recipes with that ingredient, null if list is empty */ 
    public static List<KeyWithWeight> recipeByIngredientsORefficient(List<Key> ings,Accounts acc, short kosher,String type, int reqNum,List<String> forbiddenSites,int cookTimeLimit){ 
    	SortedSet<KeyWithWeight> temp = new TreeSet<KeyWithWeight>();
        List<KeyWithWeight> res = new LinkedList<KeyWithWeight>(); 
        List<RecipeInfo> infosFiltered = new LinkedList<RecipeInfo>();  
        int size = ings.size();                 
        int sizeNeed = (reqNum+1)*PAGE_SIZE;
        int siteSize=0;
        
        //search more if there is a filter
        if(!type.equals("הכל")) sizeNeed = 3*sizeNeed; 
        //kosher filter is considered in getRecipeInfosForOR..
        for(String site : forbiddenSites) {
        	if(site.startsWith("http://www.kipa.co.il")||site.startsWith("http://www.chef-lavan.co.il")||site.startsWith("http://www.rotev.co.il"))
        		siteSize++;
        }
        if(siteSize==1) sizeNeed = sizeNeed*5/4;
        else if(siteSize>1) sizeNeed = sizeNeed*2;
        
        int numOfRecipeInfos = sizeNeed*2; //not suppose to stay
        if(size==2) numOfRecipeInfos = sizeNeed*2;
        else if(size==3) numOfRecipeInfos = sizeNeed*2;
        else if(size>3 && size<7) numOfRecipeInfos = sizeNeed*3/2;
        else if(size>7 && size<17) numOfRecipeInfos = sizeNeed;
        else if(size>16) numOfRecipeInfos = sizeNeed/2;
        
        infosFiltered=getRecipeInfosForOR(ings, numOfRecipeInfos,null, kosher, type,forbiddenSites,cookTimeLimit);
        for(RecipeInfo r : infosFiltered) 
        	temp.add(new KeyWithWeight(r,ings,acc));
        res.addAll(temp);
        return res;
    }
    
    /** search the data store for recipes by a list of ingredients   
     *  @pre ings.size>0 
     *  @return a list of recipes with must ingredient, null if list is empty */         
    	public static List<KeyWithWeight> recipeByIngredientsAND(List<Key> musts, List<Key> ings,Accounts acc, short kosher, String type,int reqNum,List<String> forbiddenSites,int cookTimeLimit){
            
    		SortedSet<KeyWithWeight> temp = new TreeSet<KeyWithWeight>();
            List<KeyWithWeight> res = new LinkedList<KeyWithWeight>(); 
            List<RecipeInfo> infosFiltered = new LinkedList<RecipeInfo>();  
            int size = musts.size();                 
            int sizeNeed = (reqNum+1)*PAGE_SIZE;
            int siteSize=0;
            
            int numOfRecipeInfos;
//            numOfRecipeInfos = sizeNeed*8;
//            if(size==1) {
//            	if(ings.size()<4 && kosher==KosherType.ALL && type.equals("הכל")) numOfRecipeInfos = sizeNeed*3;
//            	else numOfRecipeInfos = sizeNeed*6;
//            }
            
            numOfRecipeInfos = sizeNeed*2*size;
            if(kosher!=KosherType.ALL) numOfRecipeInfos = numOfRecipeInfos + sizeNeed*2;
            if(!type.equals("הכל")) numOfRecipeInfos = numOfRecipeInfos*3;
            for(String site : forbiddenSites) {
            	if(site.startsWith("http://www.kipa.co.il")||site.startsWith("http://www.chef-lavan.co.il")||site.startsWith("http://www.rotev.co.il"))
            		siteSize++;
            }
            if(siteSize==1) numOfRecipeInfos = numOfRecipeInfos*3/2;
            else if(siteSize>1) numOfRecipeInfos = numOfRecipeInfos*3;
            numOfRecipeInfos = Math.min(numOfRecipeInfos,sizeNeed*20);
            
    //        List<Ingredient> mustIngs = getIngredientsFromKeysForAND(musts,sizeNeed*4); 
            int maxResults = sizeNeed*2;
            infosFiltered=getRecipeInfosForAND(musts, numOfRecipeInfos, maxResults, kosher, type,forbiddenSites,cookTimeLimit);
            if(ings.size()==0) {
	            for(RecipeInfo r : infosFiltered) 
	            	temp.add(new KeyWithWeight(r,musts,acc));
	            }
            else {
                for(RecipeInfo r : infosFiltered) 
                	temp.add(new KeyWithWeight(r,ings,acc));
                }
            res.addAll(temp);
            return res;
    	}
/*            
            
            boolean filters = true;
            if(kosher==KosherType.ALL && type.equals("הכל")) filters=false;
          
            i=0;

            
            if(size==1) { 
//                      infos = getIngredientFromKey(musts.get(0)).getInRecipes(); 
                    infos = mustIngs.get(0).getFirstInRecipes(0,sizeNeed*4); 
                    for(RecipeInfo r : infos) 
                    	if(r.suitFilters(kosher,type)) infosFiltered.add(r);
                    if(infosFiltered.size()>sizeNeed*3)infosFiltered = infosFiltered.subList(0,sizeNeed*3);
                    for(RecipeInfo r : infosFiltered) 
                    	temp.add(new KeyWithWeight(r,ings,acc)); //for each recipe: filter,calc weight and add            
            } 
            else if(size==2) { 
                    //check who has less recipes
            		int z = mustIngs.get(0).getInRecipesPositions().size() < mustIngs.get(1).getInRecipesPositions().size() ? 0 : 1 ;
            	
            		if(filters) {
            		//1st ing (less recipes)
                    infos = mustIngs.get(z).getFirstInRecipes(0,sizeNeed*3);
                    for(RecipeInfo r : infos) 
                    	if(r.suitFilters(kosher,type)) infosFiltered.add(r);
//                    if(infosFiltered.size()>sizeNeed*3)infosFiltered = infosFiltered.subList(0,sizeNeed*3);
                    //2nd ing 
                    infos = mustIngs.get(1-z).getFirstInRecipes(0,sizeNeed*4);
                    for(RecipeInfo r : infos) 
                    	if(r.suitFilters(kosher,type)) infosFiltered2.add(r);
        //            if(infosFiltered2.size()>sizeNeed*4)infosFiltered2 = infosFiltered2.subList(0,sizeNeed*4);
                    
                    //infos = infosFiltered.retainAll(infosFiltered2);
                    infos = new LinkedList<RecipeInfo>();
                    for(RecipeInfo ri : infosFiltered)
                    	for(RecipeInfo ri2 : infosFiltered2) 
                    		if(ri.getLink().equals(ri2.getLink())) {
                    			infos.add(ri);
                    			break;
                    		}
            		}
            		else {
            			//1st ing (less recipes)
            			infosFiltered = mustIngs.get(z).getFirstInRecipes(0,sizeNeed*3);
                        //2nd ing 
            			infosFiltered2 = mustIngs.get(1-z).getFirstInRecipes(0,sizeNeed*4);
                        
                        //infos = infosFiltered.retainAll(infosFiltered2);
                        infos = new LinkedList<RecipeInfo>();
                        for(RecipeInfo ri : infosFiltered)
                        	for(RecipeInfo ri2 : infosFiltered2) 
                        		if(ri.getLink().equals(ri2.getLink()) && (!Debug.isBannedRecipe(ri.getLink()))) {
                        			infos.add(ri);
                        			break;
                        		}
                		}	
            		
            		
                    if(infos.size()>sizeNeed*3)infos = infos.subList(0,sizeNeed*3);
                    for(RecipeInfo r : infos) 
                    	temp.add(new KeyWithWeight(r,ings,acc)); //for each recipe: filter,calc weight and ad
                    } 
                else if(size>2) { 
                        int[] min = new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE};  
                        for(i=0 ; i<size ; i++) { //create min to be an array of 3 ings indexes with least recipes 
                                x=mustIngs.get(i).getNumOfRecipes(); 
                                if(x<min[0]) { 
                                        min[2]=min[1]; 
                                        min[1]=min[0]; 
                                        min[0]=i; 
                                } 
                                else if(x<min[1]) { 
                                        min[2]=min[1]; 
                                        min[1]=i; 
                                } 
                                else if(x<min[2]) { 
                                        min[2]=i; 
                                } 
                        } 
                         
                        //1st ing 
                        infos = mustIngs.get(min[0]).getInRecipes(); 
                        for(RecipeInfo r : infos) 
                        if(r.suitFilters(kosher,type)) temp.add(new KeyWithWeight(r,ings,acc)); //for each recipe: filter,calc weight and add 
                        //2nd ing 
                        infos = mustIngs.get(min[1]).getInRecipes(); 
  //                      temp = intersection(temp,infos,kosher,type); 
                        //3nd ing 
                        infos = mustIngs.get(min[2]).getInRecipes(); 
  //                      temp = intersection(temp,infos,kosher,type); 
                        //by now temp should be small 
                        for(i=0 ; i<size ; i++) { 
                                if(i==min[0] || i==min[1] || i==min[2]) continue; 
                                infos = mustIngs.get(i).getInRecipes(); 
//                                temp = intersection(temp,infos,kosher,type); 
                        } 
                } 
  //              Collections.sort(temp); 
                res.addAll(temp);
                return res; 
    }  

	*/
	/** intersection of 2 lists
	 * @param list1 - the list you already have
	 * @param list2 - a list of RecipeInfo with which you delete from list1.
	 * @param kosher - one of the filters (can have more)
	 */
	static List<KeyWithWeight> intersection(List<KeyWithWeight> list1,List<RecipeInfo> list2,short kosher,String type) {
		List<KeyWithWeight> temp = new ArrayList<KeyWithWeight>();

		for(RecipeInfo r : list2) {   		
			if(r.suitFilters(kosher,type)) //check if recipe pass the filters
    			for(KeyWithWeight kw : list1) {
    				if(r.getKey().equals(kw.getKey())) { //if recipe exist in both - add
    					temp.add(kw);
    					break;
    				}
    			}
			}
		return temp;
	}
	
	/** get the ingredient from DataStore whose key=input */
	public static Ingredient getIngredientFromKey(Key k) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient x = null,res=null;
		try {
			x = pm.getObjectById(Ingredient.class, k);
			res = new Ingredient(x);
			
		} catch(Exception e){
        	System.out.println("!can't getIngredientFromKey! details: "+e.getMessage());
		} finally {    
	        pm.close(); 
	        }
		return res;
	}
	
	/** get the ingredient from DataStore whose key=input */
	public static Ingredient getIngredientFromKey(Key k,int numOfRecipeInfos) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient x = null,res=null;
		try {
			x = pm.getObjectById(Ingredient.class, k);
			res = new Ingredient(x,numOfRecipeInfos);
			
		} catch(Exception e){
        	System.out.println("!can't getIngredientFromKey! details: "+e.getMessage());
		} finally {    
	        pm.close(); 
	        }
		return res;
	}
	
	/* get the ingredient from DataStore whose key=input *
	public static Ingredient getIngredientFromKeyForAND(Key k,int numOfRecipeInfos,List<Key> otherMustIngs) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient x = null,res=null;
		try {
			x = pm.getObjectById(Ingredient.class, k);
			res = new Ingredient(x,numOfRecipeInfos,otherMustIngs);
			
		} catch(Exception e){
        	System.out.println("!can't getIngredientFromKey! details: "+e.getMessage());
		} finally {    
	        pm.close(); 
	        }
		return res;
	}*/
	
	/** get the recipe from DataStore whose key=input */
	public static Recipes getRecipeFromKey(Key k,boolean toIncreaseView) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes x = null,res=null;
		try {
			x = pm.getObjectById(Recipes.class, k);
			//System.out.println("DS: alters.size = "+x.getAlters().size());
			res = new Recipes(x);
			if(toIncreaseView) x.increaseNumOfViews();
		} catch(Exception e){
        	System.out.println("!can't getIngredientFromKey! details: "+e.getMessage());
		} finally {
	        pm.close();
	    }
		return res;
	}
	
	/** add a single ingredient to the data store */
	public static void addIngredient(Ingredient ing) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(ing);
        } catch(Exception e){
        	System.out.println("!can't make ingredient persistent! details: "+e.getMessage());
        }
        finally {
            pm.close();
        }
	}
	
	/** returns an Ingredient by it's name */
	public static Ingredient getIngredientByName(String ingName){
		
		Key k = KeyFactory.createKey(Ingredient.class.getSimpleName(), ingName);
		return getIngredientFromKey(k);
	}
	
	/** @return all the ingredients*/
	@SuppressWarnings("unchecked")
	public static List<Ingredient> allIngredients(){
		PersistenceManager pm = PMF.get().getPersistenceManager();
                
		List<Ingredient> results = new ArrayList<Ingredient>();
        Query query = pm.newQuery(Ingredient.class);
        query.setRange(0,220);
        List<Ingredient> r1 = (List<Ingredient>) query.execute();
        query.setRange(221,440);
        List<Ingredient> r2 = (List<Ingredient>) query.execute();
        query.setRange(441,700);
        List<Ingredient> r3 = (List<Ingredient>) query.execute();
        
        results.addAll(r1);
        results.addAll(r2);
        results.addAll(r3);
        
        query.closeAll();
        pm.close();
        if(results!=null && results.iterator().hasNext())
        	return results;
        else
        	return null;
	}
	
	/** deletes all of the ingredients */
	public static void deleteAllIngredients(){
		System.out.println("deleteAllIngredients: start");
		PersistenceManager pm = PMF.get().getPersistenceManager();
       
        Query query2 = pm.newQuery(Ingredient.class);
        
        try {
        	query2.deletePersistentAll();
        } catch(Exception e){
        	System.out.println("!can't delete all ingredients! details: "+e.getMessage());
        }
        finally {
        	query2.closeAll();
        }
     
        System.out.println("deleteAllIngredients: end");
	}
	
	/** deletes all of the ingWithAlters */
	public static void deleteAllIngWithAlters(){
		System.out.println("deleteAllIngredients: start");
		PersistenceManager pm = PMF.get().getPersistenceManager();      
        Query query2 = pm.newQuery(ingWithAlters.class);        
        try {
        	query2.deletePersistentAll();
        } catch(Exception e){
        	System.out.println("!can't delete all ingWithAlters! details: "+e.getMessage());
        }
        finally {
        	query2.closeAll();
        }
        System.out.println("deleteAllIngredients: end");
	}
	
	/** deletes all of the RecipeInfos */
	public static void delete400RecipeInfos(){
		System.out.println("deleteAllIngredients: start");
		PersistenceManager pm = PMF.get().getPersistenceManager();
        Query query2 = pm.newQuery(RecipeInfo.class);
        query2.setRange(0,400);
        try {
        	query2.deletePersistentAll();
        } catch(Exception e){
        	System.out.println("!can't delete all ingWithAlters! details: "+e.getMessage());
        }
        finally {
        	query2.closeAll();
        }
        System.out.println("deleteAllIngredients: end");
	}

	/** Add all ingredients in List to the datastore*/
	public static void addAllIngredients(List<Ingredient> toDS) {
		List<Ingredient> sub;
		for(int i=0 ; 100*i<toDS.size() ; i++) {
			sub = toDS.subList(100*i, Math.min(toDS.size(),100*(i+1)));
			add100Ingredients(sub);
		}
		System.out.println("DS: added all "+toDS.size()+" ingredients to the DS");
	}
	
	/** Add all ingredients in List to the datastore*/
	/**@pre collection size should be <= 100*/
	private static void add100Ingredients(List<Ingredient> toDS) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistentAll(toDS);
        } catch(Exception e){
        	System.out.println("!can't make ingredient persistent! details: "+e.getMessage());
        }
        finally {
            pm.close();
        }
		
	}
	
	/** get the recipes from DataStore whose keys in the list */
	@SuppressWarnings("unchecked")
	public static List<Recipes> getRecipesFromKeysEfficiently(List<Key> keys) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery("select from " + Recipes.class.getName() + " where key == :keys");
	    List<Recipes> tmp = (List<Recipes>) q.execute(keys);
	    List<Recipes> res = new ArrayList<Recipes>();
	    for(Recipes rec : tmp) res.add(new Recipes(rec));
		q.closeAll();
		pm.close();
		return res;
	}
	
	/** get the recipes from DataStore whose keys in the list */
	public static List<Recipes> getRecipesFromKeys(List<Key> keys) {
		List<Recipes> res = new ArrayList<Recipes>();
		for(Key k : keys)
			res.add(DataStore.getRecipeFromKey(k,false));
		return res;
	}
	
	/** get the ingredients from DataStore whose keys in the list */
	@SuppressWarnings("unchecked")
	public static List<Ingredient> getIngredientsFromKeys(List<Key> keys) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery("select from " + Ingredient.class.getName() + " where key == :keys");
	    List<Ingredient> tmp = (List<Ingredient>) q.execute(keys);
	    List<Ingredient> res = new ArrayList<Ingredient>();
	    for(Ingredient ing : tmp) res.add(new Ingredient(ing));
		q.closeAll();
		pm.close();
		return res;
	}
	
	/** get the ingredients from DataStore whose keys in the list */
	@SuppressWarnings("unchecked")
	public static List<Ingredient> getIngredientsFromKeys(List<Key> keys, int numOfRecipeInfos) {
		List<Key> fails = new LinkedList<Key>();
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery("select from " + Ingredient.class.getName() + " where key == :keys");
	    List<Ingredient> tmp = (List<Ingredient>) q.execute(keys);
	    List<Ingredient> res = new ArrayList<Ingredient>();
	    for(Ingredient ing : tmp) {
	    	if(ing.getInRecipesPositions()==null || ing.getInRecipesPositions().size()!=ing.getInRecipes().size()){
	    		System.out.println("pos was null");
	    		fails.add(ing.getKey());
	    	}
	    	else res.add(new Ingredient(ing,numOfRecipeInfos));
	    }
		q.closeAll();
		pm.close();
		for(Key k : fails){
			updateIngredientInRecipesPositions(k);
	        res.add(getIngredientFromKey(k,numOfRecipeInfos));
	            
	        }
		return res;
	}
	
	/* get the ingredients from DataStore whose keys in the list *
	@SuppressWarnings("unchecked")
	public static List<Ingredient> getIngredientsFromKeysForAND(List<Key> keys, int numOfRecipeInfos) {
		List<Key> fails = new LinkedList<Key>();
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery("select from " + Ingredient.class.getName() + " where key == :keys");
	    List<Ingredient> tmp = (List<Ingredient>) q.execute(keys);
	    List<Ingredient> res = new ArrayList<Ingredient>();
	    for(Ingredient ing : tmp) {
	    	if(ing.getInRecipesPositions()==null || ing.getInRecipesPositions().size()!=ing.getInRecipes().size()){
	    		System.out.println("pos was null");
	    		fails.add(ing.getKey());
	    	}
	    	else res.add(new Ingredient(ing,numOfRecipeInfos,keys));
	    }
		q.closeAll();
		pm.close();
		for(Key k : fails){
			updateIngredientInRecipesPositions(k);
	        res.add(getIngredientFromKeyForAND(k,numOfRecipeInfos,keys));
	            
	        }
		return res;
	}*/
	
	/** get the ingredients from DataStore whose keys in the list
	 * links should be null */
	@SuppressWarnings("unchecked")
	public static List<RecipeInfo> getRecipeInfosForOR(List<Key> keys, int numOfRecipeInfos,SortedSet<String> links, short kosher,String type,List<String> forbiddenSites,int cookTimeLimit) {
		List<Key> fails = new LinkedList<Key>();
		if(links==null) links = new TreeSet<String>();
		List<RecipeInfo> ris = new ArrayList<RecipeInfo>();
		Ingredient curIng;
		int temp_numOfRecipeInfos;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery("select from " + Ingredient.class.getName() + " where key == :keys");
	    List<Ingredient> tmp = (List<Ingredient>) q.execute(keys);
	    for(Ingredient ing : tmp) {
	    	if(ing.getInRecipesPositions()==null || ing.getInRecipesPositions().size()!=ing.getInRecipes().size()){
	    		System.out.println("pos was null");
	    		fails.add(ing.getKey());
	    	}
	    	else {
	    		temp_numOfRecipeInfos = numOfRecipeInfos;
	    		//search more if there is a kosher filter
	    		if(kosher==KosherType.MEAT)
	    			if(ing.getKosher()!=KosherType.MEAT)
	    				temp_numOfRecipeInfos = numOfRecipeInfos*2;
	    		if(kosher==KosherType.DAIRY)
	    			if(ing.getKosher()!=KosherType.DAIRY)
	    				temp_numOfRecipeInfos = numOfRecipeInfos*2;
	    		
	    		curIng = new Ingredient(ing,temp_numOfRecipeInfos,links,kosher,type,forbiddenSites,cookTimeLimit);
	    		for(RecipeInfo ri : curIng.getInRecipes()) {
	    			links.add(ri.getLink());
	    			ris.add(ri);
	    		}
	    	}
	    }
		q.closeAll();
		pm.close();
		if(fails.size()>0) {
			for(Key k : fails){
				updateIngredientInRecipesPositions(k);
		        }
			ris.addAll(getRecipeInfosForOR(fails,numOfRecipeInfos,links,kosher,type,forbiddenSites,cookTimeLimit));
		}
		return ris;
	}
	
	/** get the ingredients from DataStore whose keys in the list */
	public static List<RecipeInfo> getRecipeInfosForAND(List<Key> ings, int numOfRecipeInfos,int maxResults,short kosher,String type,List<String> forbiddenSites,int cookTimeLimit) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient x = null,res=null;
		boolean fail=false;
		try {
			x = pm.getObjectById(Ingredient.class, ings.get(0));
			if(x.getInRecipesPositions()==null || x.getInRecipesPositions().size()!=x.getInRecipes().size()){
	    		System.out.println("pos was null");
	    		fail=true;
	    	}
			else res = new Ingredient(x,numOfRecipeInfos,maxResults, ings,kosher,type,forbiddenSites,cookTimeLimit);
			
		} catch(Exception e){
        	System.out.println("!can't getIngredientFromKey! details: "+e.getMessage());
		} finally {    
	        pm.close(); 
	        }
		
		if(fail) {
			updateIngredientInRecipesPositions(ings.get(0));
			return getRecipeInfosForAND(ings,numOfRecipeInfos,maxResults,kosher,type,forbiddenSites,cookTimeLimit);
		}
		return res.getInRecipes();
	}

	/** add RecipeInfo object to the list in every ingredient needed
	 * @when it's used after the Recipe is created*/
	public static void updateRecipeInfo(Recipes recipe) {
		List<Key> ing_keys = recipe.getListOfIngredientsAndAlters();		
		removeDuplicate_Key(ing_keys);		
		RecipeInfo ri;
		for(Key k : ing_keys) {
			ri = new RecipeInfo(recipe);
			addRecipeInfoInIngredient(k,ri);
		}
	}
	
	/** add RecipeInfo object to the list in the ingredient specified
	 * @when it's used after the Recipe is created*/
	private static void addRecipeInfoInIngredient(Key ing_key,RecipeInfo ri) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient ingredient=null;
		boolean need = true;
		try {
			ingredient = pm.getObjectById(Ingredient.class, ing_key);
			//check if it already exists (if we deleted the recipes without updating the ings)
			for(RecipeInfo r : ingredient.getInRecipes())
				if(r.getLink().equals(ri.getLink())) {
					need=false;
					break;
				}
			if(need) ingredient.addInRecipes(ri);//add RecipeInfo to the list of every ingredient	
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfo! details: "+e.getMessage());
		} finally {		
			
	    }
		pm.close();//close the pm saving the changes
	}
	
	/**update Globals object, or create a new one if it hasb't been created*/
	public static void updateGlobals(Globals g) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Globals x=null;
		Key k = KeyFactory.createKey(Globals.class.getSimpleName(), 1);
		try {
			x = pm.getObjectById(Globals.class, k);
			
		} catch(Exception e){
        	System.out.println("!can't get Globals from DS! details: "+e.getMessage());
        	
        	//if it's first time and globals doesn't exists, make one.
        	try {
	            pm.makePersistent(g);
	            System.out.println("inserted new Globals to DS");
	        } catch(Exception e2){
	        	System.out.println("!can't make Globals persistent! details: "+e2.getMessage());
	        }
        	
		} finally {	
			x.updateFields(g);
//			System.out.println("updated Globals in DS. "+ x.getMostViewedNumber().get(0));
			pm.close();//close the pm saving the changes
	    }	
	}
	
	/** get Globals from DS */
	public static Globals getGlobals() {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Globals x = null,res=null;
		Key k = KeyFactory.createKey(Globals.class.getSimpleName(), 1);
		try {
			x = pm.getObjectById(Globals.class, k);
			res = new Globals(x);	
//			System.out.println("DS: week=" + res.getWeekNumber());
		} catch(Exception e){
        	System.out.println("!can't get Globals object (it's ok if DS was totally empty)! details: "+e.getMessage());
		} finally {
	        pm.close();
	    }
		if(res==null) return new Globals();
		return res;
	}

	/** add alternative ingredient to base ing in recipe rec_key
	 * @return true if it's a legal alternative ingredient*/
	public static boolean addAlterToRecipe(Key rec_key, Key base , Key alter) {		
		Ingredient alter_ing = getIngredientFromKey(alter);
		short alter_kosher = alter_ing.getKosher();
		List<Key> ings = new ArrayList<Key>();
		String rec_link="";
		RecipeInfo ri;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes rec=null;
		try {
			rec = pm.getObjectById(Recipes.class, rec_key);
			
			//check if alter kosher suitable to reipce kosher
			if((rec.getKosher()==KosherType.DAIRY && alter_kosher==KosherType.MEAT) || (rec.getKosher()==KosherType.MEAT && alter_kosher==KosherType.DAIRY)) return false;

			rec.addAlternative(base, alter); //add alternative in recipe
			} catch(Exception e){
        	System.out.println("!can't addAlterToRecipe! details: "+e.getMessage());
        	return false;
		} finally {
						
			
			
			System.out.println("in rec size -- "+rec.getAlters().size());
			pm.close();//close the pm saving the changes
	    }
		rec = DataStore.getRecipeFromKey(rec_key,false);
		ings.addAll(rec.getIngredients());
		rec_link = rec.getLink();
		
		
		for(Key ing_k : ings){ //update RI of all ings in recipe
			updateRecipeInfoAlters(ing_k,rec_link,base,alter);
		}
		ri = new RecipeInfo(rec);
		addRecipeInfoInIngredient(alter,ri); //add RI to the new alter ing	
		return true;
	}
	
	/** updates ingredients' recipeInfo's alters
	 * @param ing_k - the key of the ingredient containing RI
	 * @param liwa - the recipe's Alters list
	 * @param link - the link of the recipe whose grade was changed
	 */
	private static void updateRecipeInfoAlters(Key ing_k,String link, Key base, Key alter) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient ing=null;
		int i=0;
		try {
			ing = pm.getObjectById(Ingredient.class, ing_k);
			for(RecipeInfo ri :ing.getInRecipes()) {
				if((ri.getLink()).equals(link)) {
					ing.getInRecipes().get(i).addAlternative(base, alter);
					break;
				}	
				i++;
			}
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfoAlters! details: "+e.getMessage());
		} finally {	
			System.out.println("in ri size -- "+ing.getInRecipes().get(0).getAlters().size());
			pm.close();//close the pm saving the changes
	    }
	}
	
	/** add alternative ingredient to base ing in recipe rec_key*/
	public static void addGroupToRecipe(Key rec_key, Recipes original) {		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes rec=null;
		try {
			rec = pm.getObjectById(Recipes.class, rec_key);
			} catch(Exception e){
        	System.out.println("!can't addAlterToRecipe! details: "+e.getMessage());
		} finally {		
			rec.addAlternatives(original.getAlters());
	    }
		pm.close();//close the pm saving the changes
	}
	
	/** add new account to the datastore */
	public static void addNewAccount (User user){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts account = new Accounts(user);
        try {
            pm.makePersistentAll(account);
            System.out.println("added new user to DS - "+ user.getEmail());
        } catch(Exception e){
        	System.out.println("!can't make ingredient persistent! details: "+e.getMessage());
        }
        finally {
            pm.close();
        }
	}
	
	/** get the Account from DataStore whose mail=input.
	 * if it's a new user, it also creates his account */
	public static Accounts getAccountOfUser(User user) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts x = null,res=null;
		try {
			x = pm.getObjectById(Accounts.class, KeyFactory.createKey(Accounts.class.getSimpleName(), user.getEmail()));
			System.out.println("DS: user mail = "+x.getUserMail() + ", has fridge size = "+ x.getMyFridge().size());
			res = new Accounts(x);
		} catch(Exception e){
        	System.out.println("!can't getAccountFromMail! (which is ok if it's his first time in the site) details: "+e.getMessage());
        	DataStore.addNewAccount(user);
        	res = new Accounts(user);
		} finally {
	        pm.close();
	    }
		return res;
	}

	/** deletes all of the accounts */
	public static void deleteAllAccounts(){
		System.out.println("deleting all accounts");
		PersistenceManager pm = PMF.get().getPersistenceManager();
       
        Query query = pm.newQuery(Accounts.class);
        
        query.deletePersistentAll();
        
        query.closeAll();
        System.out.println("all accounts had been deleted");
	}

	/** updates the account's frigde */
	public static void updateAccountFridge(String mail, List<String> ings){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			acc.setMyFridge(ings);
			System.out.println("DS: set fridge size to = "+acc.getMyFridge().size());
		} catch(Exception e){
        	System.out.println("!can't updateAccountFridge! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	/** add a ranking to the account.
	 * @return his previous rate if he already rated it. 0 if success. */
	public static int addAccountRankings(String mail, String recipe_link, int rank){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		int res=0;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			if((res=(acc.getRankForRecipe(recipe_link)))!=0) return res; //user already rated it
			acc.addRankRecipe(recipe_link, rank);
			System.out.println("DS: set rank size to = "+acc.getRankedRecipes_num().size());
		} catch(Exception e){
        	System.out.println("!can't updateAccountFridge! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	    return 0;
	}
	
	/** get the user request whether to use the fridge */
	public static boolean getUserFridgeRequest(String mail){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc = null;
		boolean req = false;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			System.out.println("the user request is: "+acc.getUserFridgeReq());
			req = acc.getUserFridgeReq();
		} catch(Exception e){
        	System.out.println("!can't get user request! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
		return req;
	}
	
	/** set the user request whether to use the fridge */
	public static boolean setUserFridgeRequest(String mail, boolean req){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		boolean res=false;
		try {
			acc = pm.getObjectById(Accounts.class, key);
			System.out.println("the user request is: "+req);
			acc.setUserFridgeReq(req);
			res = true;
		} catch(Exception e){
        	System.out.println("!can't set user request! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
		return res;
	}
	
	/** add a viewed recipe to the account.*/
	public static void addAccountViewedRecipes(String mail, String recipe_link, String recipe_name){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			acc.addViewedRecipe(recipe_name, recipe_link);
			System.out.println("viewed recipes size = "+acc.getViewedRecipes_name().size());
		} catch(Exception e){
        	System.out.println("!can't updateAccountFridge! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	/** add a favorite recipe to the account.
	 * return true if added successfully, false if recipe was already in fav*/
	public static boolean addAccountFavoriteRecipes(String mail, String recipe_link, String recipe_name, String uploader){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		boolean bol=false;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			bol = acc.addFavorite(recipe_link, recipe_name, uploader);
			System.out.println("viewed recipes size = "+acc.getViewedRecipes_name().size());
		} catch(Exception e){
        	System.out.println("!can't updateAccountFridge! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
		return bol;
	}
	
	/** remove a favorite recipe to the account.*/
	public static void removeAccountFavoriteRecipes(String mail, String recipe_link, String uploader){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Accounts acc=null;
		Key key = KeyFactory.createKey(Accounts.class.getSimpleName(), mail);
		try {
			acc = pm.getObjectById(Accounts.class, key);
			acc.removeFavorite(recipe_link, uploader);
			System.out.println("viewed recipes size = "+acc.getViewedRecipes_name().size());
		} catch(Exception e){
        	System.out.println("!can't updateAccountFridge! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	/** add a comment to the recipe.
	 * return true if added successfully, false if recipe was already in fav*/
	public static void addCommentToRecipe(String recipe_link, String comment, String uploader){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes rec=null;
		try {
			rec = pm.getObjectById(Recipes.class, KeyFactory.createKey(Recipes.class.getSimpleName(), recipe_link));
			rec.addComment(comment,uploader);
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfo! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	
	/** updates Ingredient InRecipesPositions. needs to be done only once to each ing. */
	public static void updateIngredientInRecipesPositions(Key ing_key){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient ingredient=null;
		try {
			ingredient = pm.getObjectById(Ingredient.class, ing_key);
			ingredient.updatePositions();
			System.out.println("DS: updated InRecipesPositions of size "+ingredient.getInRecipesPositions().size());
		} catch(Exception e){
        	System.out.println("!can't updateIngredientInRecipesPositions! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}
	
	
	/** reset views and set a new week */
	public static void resetRecipeViews(Key k, int week) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes x = null;
		try {
			x = pm.getObjectById(Recipes.class, k);
			x.setNumOfViews(1);
			x.setWeekNumber(week);
			
		} catch(Exception e){
        	System.out.println("!can't resetRecipeViews! details: "+e.getMessage());
		} finally {
	        pm.close();
	    }
	}
	
	/** updates the recipe and recipeInfos Cook Time,
	 * @return the new recipe Cook Time, or -1 if user already entered one */
	public static int updateRecipeCookTime(String link, int userCookTime, String user){
		int g=-1;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Recipes rec=null;
		List<Key> ings = new ArrayList<Key>();
		Key key = KeyFactory.createKey(Recipes.class.getSimpleName(), link);
		try {
			rec = pm.getObjectById(Recipes.class, key);
			g=rec.addCookTime(userCookTime,user);
			ings.addAll(rec.getIngredients());
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeCookTime! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
		if(g>=0)
			for(Key k : ings) updateRecipeInfoCookTime(k,g,link);
		System.out.println("DS: g= "+g);
		return g;
	}
	
	/** updates ingredients' recipeInfo's grade
	 * @param ing_k - the key of the ingredient containing RI
	 * @param g - the grade to be set
	 * @param link - the link of the recipe whose grade was changed
	 */
	private static void updateRecipeInfoCookTime(Key ing_k, int g,String link) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ingredient ing=null;
		try {
			ing = pm.getObjectById(Ingredient.class, ing_k);
			for(RecipeInfo ri :ing.getInRecipes()) {
				if((ri.getLink()).equals(link)) {
					ri.setCookTime(g);
					break;
				}		
			}
		} catch(Exception e){
        	System.out.println("!can't getIngredientsFromKeys in updateRecipeInfoCookTime! details: "+e.getMessage());
		} finally {		
			pm.close();//close the pm saving the changes
	    }
	}

}